﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Calculator.ConsoleCalc
{
	class Parser : ConsoleCalc.IParser
	{
		protected String[] _supportedFunction;
		protected Dictionary<String, FunctionType> _operations = new Dictionary<String, FunctionType>();
		protected Dictionary<String, Int32> _priorityOperations = new Dictionary<String, Int32>();

		private Stack<Double> _operands;
		private Stack<String> _functions;

		private StringBuilder _expression;
		private Int32 _position;

		String _item;
		String _prevItem;
		private Boolean _waitResult;
        private Boolean _setResult;


		public Parser()
		{
			_supportedFunction = new String[] {"+", "-", "*", "/", "(", ")"};

			_operations.Add("+", FunctionType.Plus);
			_operations.Add("-", FunctionType.Minus);
			_operations.Add("*", FunctionType.Multiply);
			_operations.Add("/", FunctionType.Divide);

			_priorityOperations.Add("(", -1);
			_priorityOperations.Add("*", 1);
			_priorityOperations.Add("/", 1);
			_priorityOperations.Add("+", 2);
			_priorityOperations.Add("-", 2);
		}

		public void ParseExpression(String expression)
		{
            _operands = new Stack<Double>();
            _functions = new Stack<String>();
			_expression = new StringBuilder(expression);
			_expression.Insert(0, "(").Append(")");
			_position = 0;
            _prevItem = "U";
            _waitResult = false;
            _setResult = false;
		}

		public Expression GetNextExpression()
		{
			do
			{
                if (!_setResult)
				    _item = GetItem();

				// разрливаем унарный + и -
				if (isFunction(_item) && isFunction(_prevItem) && (_prevItem == "(" && (_item == "+" || _item == "-")))
					_operands.Push(0); // Добавляем нулевой элемент
				
				if (isOperand(_item))
				{
					_operands.Push(Convert.ToDouble(_item));
				}
				else if (isFunction(_item))
				{
					if (_item == ")")
					{
						// Скобка - исключение из правил. выталкивает все операции до первой открывающейся
						while (_functions.Count > 0 && _functions.Peek() != "(")
							return this.GetExpression();
						_functions.Pop(); // Удаляем саму скобку "("
					}
					else
					{
						while (canPop(_item))
							return this.GetExpression();
						_functions.Push(_item);
					}
				}
				_prevItem = _item;
                _setResult = false;
			}
			while (_item != null);

            if (_operands.Count > 1 || _functions.Count > 0)
                throw new ParserException("Ошибка в разборе выражения");

			return new Expression(0,0, FunctionType.Unknown); //Если кончилась строка — возвращаем пустое выражение
		}

		public void SetReturnResult(double Result)
		{
			if (!_waitResult)
				throw new ParserException("Нельзя возвращать результат, не запросив выражение");

			_operands.Push(Result);
			_waitResult = false;
            _setResult = true;
		}

		Expression GetExpression()
		{
			if (_waitResult)
				throw new ParserException("Нельзя запрашивать выражение, не вернув результата предыдущего");

			double B = _operands.Pop();
			double A = _operands.Pop();
			string op = _functions.Pop();
			FunctionType type = FunctionType.Unknown;

			_operations.TryGetValue(op, out type);

			_waitResult = true;
			return new Expression(A, B, type);
		}

		private bool isOperand(string item)
		{
			try
			{
				Convert.ToDouble(item);
				return item != null;
			}		
			catch (Exception) {}
			return false;
		}

		private bool isFunction(string item)
		{
			return _supportedFunction.Contains(item);
		}

		private bool canPop(string operation)
		{
			if (_functions.Count == 0)
				return false;

			int p1 = GetPriority(operation);
			int p2 = GetPriority(_functions.Peek());

			return p1 >= 0 && p2 >= 0 && p1 >= p2;
		}

		private int GetPriority(string operation)
		{
			Int32 pri = Int32.MinValue;

			_priorityOperations.TryGetValue(operation, out pri);

			if (pri == Int32.MinValue)
				throw new ParserException("Неопределенная операция");
			else
				return pri;
		}

		private string GetItem()
		{
			if (_position == _expression.Length) // конец строки
				return null;

			if (char.IsDigit(_expression[_position]))
				return GetOperand();
			else
				return GetFunction();
		}

		private string GetFunction()
		{
			return _expression[_position++].ToString();
		}

		private string GetOperand()
		{
			string res = "";

			while (_position < _expression.Length && (char.IsDigit(_expression[_position]) || _expression[_position] == ','))
				res += _expression[_position++];

			return res;
		}

	}
}
